package dataloader

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"mall/service/blog/rpc/blogclient"
	"mall/service/wpgql/api/internal/graph/model"
	"mall/service/wpgql/api/internal/graph/storage"
	"mall/service/wpgql/api/internal/svc"

	"github.com/graph-gophers/dataloader"
	gopher_dataloader "github.com/graph-gophers/dataloader"
)

// GetUser wraps the User dataloader for efficient retrieval by user ID
func GetUser(ctx context.Context, userID string) (*model.User, error) {
	loaders := For(ctx)
	if loaders == nil {
		return nil, nil
	}

	thunk := loaders.userLoader.Load(ctx, gopher_dataloader.StringKey(userID))
	result, err := thunk()
	if err != nil {
		return nil, err
	}

	output, ok := result.(*model.User)
	if !ok {
		return nil, nil
	}

	return output, nil
}

// userBatcher wraps storage and provides a "get" method for the user dataloader
type UserBatcher struct {
	storage storage.Storage
	svcCtx  *svc.ServiceContext
}

func NewUserBatcher(svcCtx *svc.ServiceContext) *UserBatcher {
	return &UserBatcher{
		storage: svcCtx.Storage,
		svcCtx:  svcCtx,
	}
}

// get implements the dataloader for finding many users by Id and returns
// them in the order requested
func (b *UserBatcher) get(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
	fmt.Printf("dataloader.userBatcher.get, users: [%s]\n", strings.Join(keys.Keys(), ","))
	// create a map for remembering the order of keys passed in
	keyOrder := make(map[string]int, len(keys))
	// collect the keys to search for
	var dbIDs []string
	for idx, key := range keys {
		dbIDs = append(dbIDs, key.String())
		keyOrder[key.String()] = idx
	}

	// search for those users
	dbRecords, err := b.storage.GetUsers(ctx, dbIDs)
	// if DB error, return
	if err != nil {
		return []*dataloader.Result{{Data: nil, Error: err}}
	}

	// construct an output array of dataloader results
	results := make([]*dataloader.Result, len(keys))
	// enumerate records, put into output
	for _, record := range dbRecords {
		if record.DatabaseID > 0 { // 缓存有记录，数据库有记录
			idx, ok := keyOrder[record.ID]
			// if found, remove from index lookup map so we know elements were found
			if ok {
				results[idx] = &dataloader.Result{Data: record, Error: nil}
				delete(keyOrder, record.ID)
			}
		} else { // 缓存有记录，数据库无记录
			idx, ok := keyOrder[record.ID]
			if ok {
				er := fmt.Errorf("user not found %s", record.ID)
				results[idx] = &dataloader.Result{Data: nil, Error: er}
				delete(keyOrder, record.ID)
			}
		}
	}

	var ids []int64 // 缓存无记录
	for dbID, _ := range keyOrder {
		if i, err := strconv.ParseInt(dbID, 10, 64); err == nil {
			ids = append(ids, i)
		}
	}
	if len(ids) > 0 {
		res, err := b.svcCtx.BlogRpc.UserList(ctx, &blogclient.UserListRequest{
			UserIn: ids,
		})
		if err != nil || len(res.Data) == 0 { // 查询失败或无数据，向缓存填充空数据，防止反复查询
			for dbID, idx := range keyOrder {
				er := b.storage.PutUser(ctx, &model.User{
					DatabaseID: 0,
					ID:         dbID,
				})
				if er != nil {
					results[idx] = &dataloader.Result{Data: nil, Error: er}
				} else {
					results[idx] = &dataloader.Result{Data: nil, Error: err}
				}
				delete(keyOrder, dbID)
			}
		} else {
			for _, user := range res.Data {
				var capabilities []*string
				if user.Capabilities != nil && len(user.Capabilities) > 0 {
					for _, cap := range user.Capabilities {
						capabilities = append(capabilities, &cap)
					}
				}
				var extraCapabilities []*string
				if user.ExtraCapabilities != nil && len(user.ExtraCapabilities) > 0 {
					for _, cap := range user.ExtraCapabilities {
						extraCapabilities = append(extraCapabilities, &cap)
					}
				}
				record := &model.User{
					CapKey:            &user.CapKey,
					Capabilities:      capabilities,
					DatabaseID:        int(user.DatabaseId),
					Description:       &user.Description,
					Email:             &user.Email,
					ExtraCapabilities: extraCapabilities,
					FirstName:         &user.FirstName,
					ID:                user.Id,
					IsRestricted:      &user.IsRestricted,
					LastName:          &user.LastName,
					Locale:            &user.Locale,
					Name:              &user.Name,
					Nicename:          &user.Nicename,
					Nickname:          &user.Nickname,
					RegisteredDate:    &user.RegisteredDate,
					Slug:              &user.Slug,
					URI:               &user.URI,
					URL:               &user.URL,
					Username:          &user.Username,
				}
				b.storage.PutUser(ctx, record)

				idx, ok := keyOrder[user.Id]
				// if found, remove from index lookup map so we know elements were found
				if ok {
					results[idx] = &dataloader.Result{Data: record, Error: nil}
					delete(keyOrder, user.Id)
				}
			}
		}
	}

	// fill array positions with errors where not found in DB
	for dbID, idx := range keyOrder {
		err := fmt.Errorf("user not found %s", dbID)
		results[idx] = &dataloader.Result{Data: nil, Error: err}
	}

	// return results
	return results
}
